{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Project: Description"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The starting point for this project is the `Polygon` class and the `Polygons` sequence type we created in the previous project.\n",
    "\n",
    "The code for these classes along with the unit tests for the `Polygon` class are below if you want to use those as your starting point. But use whatever you came up with in the last project."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We have two goals:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### Goal 1\n",
    "\n",
    "Refactor the `Polygon` class so that all the calculated properties are lazy properties, i.e. they should still be calculated properties, but they should not have to get recalculated more than once (since we made our `Polygon` class \"immutable\")."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### Goal 2\n",
    "\n",
    "Refactor the `Polygons` (sequence) type, into an **iterable**. Make sure also that the elements in the iterator are computed lazily - i.e. you can no longer use a list as an underlying storage mechanism for your polygons.\n",
    "\n",
    "You'll need to implement both an iterable, and an iterator."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### Code from Previous Project"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "import math\n",
    "\n",
    "class Polygon:\n",
    "    def __init__(self, n, R):\n",
    "        if n < 3:\n",
    "            raise ValueError('Polygon must have at least 3 vertices.')\n",
    "        self._n = n\n",
    "        self._R = R\n",
    "        \n",
    "    def __repr__(self):\n",
    "        return f'Polygon(n={self._n}, R={self._R})'\n",
    "    \n",
    "    @property\n",
    "    def count_vertices(self):\n",
    "        return self._n\n",
    "    \n",
    "    @property\n",
    "    def count_edges(self):\n",
    "        return self._n\n",
    "    \n",
    "    @property\n",
    "    def circumradius(self):\n",
    "        return self._R\n",
    "    \n",
    "    @property\n",
    "    def interior_angle(self):\n",
    "        return (self._n - 2) * 180 / self._n\n",
    "\n",
    "    @property\n",
    "    def side_length(self):\n",
    "        return 2 * self._R * math.sin(math.pi / self._n)\n",
    "    \n",
    "    @property\n",
    "    def apothem(self):\n",
    "        return self._R * math.cos(math.pi / self._n)\n",
    "    \n",
    "    @property\n",
    "    def area(self):\n",
    "        return self._n / 2 * self.side_length * self.apothem\n",
    "    \n",
    "    @property\n",
    "    def perimeter(self):\n",
    "        return self._n * self.side_length\n",
    "    \n",
    "    def __eq__(self, other):\n",
    "        if isinstance(other, self.__class__):\n",
    "            return (self.count_edges == other.count_edges \n",
    "                    and self.circumradius == other.circumradius)\n",
    "        else:\n",
    "            return NotImplemented\n",
    "        \n",
    "    def __gt__(self, other):\n",
    "        if isinstance(other, self.__class__):\n",
    "            return self.count_vertices > other.count_vertices\n",
    "        else:\n",
    "            return NotImplemented"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def test_polygon():\n",
    "    abs_tol = 0.001\n",
    "    rel_tol = 0.001\n",
    "    \n",
    "    try:\n",
    "        p = Polygon(2, 10)\n",
    "        assert False, ('Creating a Polygon with 2 sides: '\n",
    "                       ' Exception expected, not received')\n",
    "    except ValueError:\n",
    "        pass\n",
    "                       \n",
    "    n = 3\n",
    "    R = 1\n",
    "    p = Polygon(n, R)\n",
    "    assert str(p) == 'Polygon(n=3, R=1)', f'actual: {str(p)}'\n",
    "    assert p.count_vertices == n, (f'actual: {p.count_vertices},'\n",
    "                                   f' expected: {n}')\n",
    "    assert p.count_edges == n, f'actual: {p.count_edges}, expected: {n}'\n",
    "    assert p.circumradius == R, f'actual: {p.circumradius}, expected: {n}'\n",
    "    assert p.interior_angle == 60, (f'actual: {p.interior_angle},'\n",
    "                                    ' expected: 60')\n",
    "    n = 4\n",
    "    R = 1\n",
    "    p = Polygon(n, R)\n",
    "    assert p.interior_angle == 90, (f'actual: {p.interior_angle}, '\n",
    "                                    ' expected: 90')\n",
    "    assert math.isclose(p.area, 2, \n",
    "                        rel_tol=abs_tol, \n",
    "                        abs_tol=abs_tol), (f'actual: {p.area},'\n",
    "                                           ' expected: 2.0')\n",
    "    \n",
    "    assert math.isclose(p.side_length, math.sqrt(2),\n",
    "                       rel_tol=rel_tol,\n",
    "                       abs_tol=abs_tol), (f'actual: {p.side_length},'\n",
    "                                          f' expected: {math.sqrt(2)}')\n",
    "    \n",
    "    assert math.isclose(p.perimeter, 4 * math.sqrt(2),\n",
    "                       rel_tol=rel_tol,\n",
    "                       abs_tol=abs_tol), (f'actual: {p.perimeter},'\n",
    "                                          f' expected: {4 * math.sqrt(2)}')\n",
    "    \n",
    "    assert math.isclose(p.apothem, 0.707,\n",
    "                       rel_tol=rel_tol,\n",
    "                       abs_tol=abs_tol), (f'actual: {p.perimeter},'\n",
    "                                          ' expected: 0.707')\n",
    "    p = Polygon(6, 2)\n",
    "    assert math.isclose(p.side_length, 2,\n",
    "                        rel_tol=rel_tol, abs_tol=abs_tol)\n",
    "    assert math.isclose(p.apothem, 1.73205,\n",
    "                        rel_tol=rel_tol, abs_tol=abs_tol)\n",
    "    assert math.isclose(p.area, 10.3923,\n",
    "                        rel_tol=rel_tol, abs_tol=abs_tol)\n",
    "    assert math.isclose(p.perimeter, 12,\n",
    "                        rel_tol=rel_tol, abs_tol=abs_tol)\n",
    "    assert math.isclose(p.interior_angle, 120,\n",
    "                        rel_tol=rel_tol, abs_tol=abs_tol)\n",
    "    \n",
    "    p = Polygon(12, 3)\n",
    "    assert math.isclose(p.side_length, 1.55291,\n",
    "                        rel_tol=rel_tol, abs_tol=abs_tol)\n",
    "    assert math.isclose(p.apothem, 2.89778,\n",
    "                        rel_tol=rel_tol, abs_tol=abs_tol)\n",
    "    assert math.isclose(p.area, 27,\n",
    "                        rel_tol=rel_tol, abs_tol=abs_tol)\n",
    "    assert math.isclose(p.perimeter, 18.635,\n",
    "                        rel_tol=rel_tol, abs_tol=abs_tol)\n",
    "    assert math.isclose(p.interior_angle, 150,\n",
    "                        rel_tol=rel_tol, abs_tol=abs_tol)\n",
    "    \n",
    "    p1 = Polygon(3, 10)\n",
    "    p2 = Polygon(10, 10)\n",
    "    p3 = Polygon(15, 10)\n",
    "    p4 = Polygon(15, 100)\n",
    "    p5 = Polygon(15, 100)\n",
    "    \n",
    "    assert p2 > p1\n",
    "    assert p2 < p3\n",
    "    assert p3 != p4\n",
    "    assert p1 != p4\n",
    "    assert p4 == p5"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "class Polygons:\n",
    "    def __init__(self, m, R):\n",
    "        if m < 3:\n",
    "            raise ValueError('m must be greater than 3')\n",
    "        self._m = m\n",
    "        self._R = R\n",
    "        self._polygons = [Polygon(i, R) for i in range(3, m+1)]\n",
    "        \n",
    "    def __len__(self):\n",
    "        return self._m - 2\n",
    "    \n",
    "    def __repr__(self):\n",
    "        return f'Polygons(m={self._m}, R={self._R})'\n",
    "    \n",
    "    def __getitem__(self, s):\n",
    "        return self._polygons[s]\n",
    "    \n",
    "    @property\n",
    "    def max_efficiency_polygon(self):\n",
    "        sorted_polygons = sorted(self._polygons, \n",
    "                                 key=lambda p: p.area/p.perimeter,\n",
    "                                reverse=True)\n",
    "        return sorted_polygons[0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
